package com.example.demo.httpMessageConvert;

import com.example.demo.httpMessageConvert.weixin.Message;
import org.apache.commons.io.IOUtils;
import org.apache.tomcat.util.codec.binary.Base64;
import org.apache.tomcat.util.codec.binary.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.AbstractHttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.oxm.jaxb.Jaxb2Marshaller;
import org.springframework.security.crypto.codec.Hex;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.Marshaller;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import java.io.IOException;
import java.io.StringWriter;
import java.nio.charset.Charset;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Hashtable;
import java.util.Map;
import java.util.Random;

/**
 * Created by tangxinmao on 2016/9/29.
 * 用于加解密操作
 */
public class WeixinHttpMessageConverter extends AbstractHttpMessageConverter {
    @Autowired
    private Property property;

    protected boolean supports(Class aClass) {
        return Message.class.isAssignableFrom(aClass);
    }
    public WeixinHttpMessageConverter(Charset defaultCharset, MediaType... supportedMediaTypes) {
        super(defaultCharset, supportedMediaTypes);
    }

    /**
     * 对绑定数据揭秘
     *
     * @param aClass
     * @param httpInputMessage
     * @return
     * @throws IOException
     * @throws HttpMessageNotReadableException
     */
    @SuppressWarnings("Since15")
    protected Object readInternal(Class aClass, HttpInputMessage httpInputMessage) throws IOException, HttpMessageNotReadableException {
        Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
        marshaller.setClassesToBeBound(Message.class);
        Message object = (Message) marshaller.unmarshal(new StreamSource(httpInputMessage.getBody()));
        String encrypt = object.getEncrypt();
        try {
            Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
            byte[] aesKey = Base64.decodeBase64(property.getAesKey() + "=");
            SecretKeySpec key_spec = new SecretKeySpec(aesKey, "AES");
            IvParameterSpec iv = new IvParameterSpec(Arrays.copyOfRange(aesKey, 0, 16));
            cipher.init(Cipher.DECRYPT_MODE, key_spec, iv);
            // 使用BASE64对密文进行解码
            byte[] encrypted = Base64.decodeBase64(encrypt);
            byte[] original = cipher.doFinal(encrypted);
            // 去除补位字符
            byte[] bytes = PKCS7Encoder.decode(original);
            // 分离16位随机字符串,网络字节序和AppId
            byte[] networkOrder = Arrays.copyOfRange(bytes, 16, 20);
            int xmlLength = recoverNetworkBytesOrder(networkOrder);
            String xmlContent = new String(Arrays.copyOfRange(bytes, 20, 20 + xmlLength),  Charset.forName("utf-8"));
            Message message = (Message) marshaller.unmarshal(new StreamSource(IOUtils.toInputStream(xmlContent,Charset.forName("UTF-8"))));
            String from_appid = new String(Arrays.copyOfRange(bytes, 20 + xmlLength, bytes.length),
                    Charset.forName("utf-8"));
            return message;

        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (NoSuchPaddingException e) {
            e.printStackTrace();
        } catch (InvalidAlgorithmParameterException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        } catch (BadPaddingException e) {
            e.printStackTrace();
        } catch (IllegalBlockSizeException e) {
            e.printStackTrace();
        }

        return object;
    }

    // 还原4个字节的网络字节序
    private int recoverNetworkBytesOrder(byte[] orderBytes) {
        int sourceNumber = 0;
        for (int i = 0; i < 4; i++) {
            sourceNumber <<= 8;
            sourceNumber |= orderBytes[i] & 0xff;
        }
        return sourceNumber;
    }

    // 随机生成16位字符串
    private String getRandomStr() {
        String base = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
        Random random = new Random();
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < 16; i++) {
            int number = random.nextInt(base.length());
            sb.append(base.charAt(number));
        }
        return sb.toString();
    }

    // 生成4个字节的网络字节序
    byte[] getNetworkBytesOrder(int sourceNumber) {
        byte[] orderBytes = new byte[4];
        orderBytes[3] = (byte) (sourceNumber & 0xFF);
        orderBytes[2] = (byte) (sourceNumber >> 8 & 0xFF);
        orderBytes[1] = (byte) (sourceNumber >> 16 & 0xFF);
        orderBytes[0] = (byte) (sourceNumber >> 24 & 0xFF);
        return orderBytes;
    }

    /**
     * 对返回数据进行加密
     * @param o
     * @param httpOutputMessage
     * @throws IOException
     * @throws HttpMessageNotWritableException
     */
    @SuppressWarnings("Since15")
    protected void writeInternal(Object o, HttpOutputMessage httpOutputMessage) throws IOException, HttpMessageNotWritableException {
        ByteGroup byteCollector = new ByteGroup();
        byte[] randomStrBytes = getRandomStr().getBytes( Charset.forName("utf-8"));
        Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
        Map map = new Hashtable();
        map.put(Marshaller.JAXB_ENCODING, "UTF-8");// 编码格式
        map.put(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        map.put(Marshaller.JAXB_FRAGMENT, true);
        marshaller.setMarshallerProperties(map);
        marshaller.setClassesToBeBound(Message.class);
        StringWriter stringWriter = new StringWriter();
        StreamResult streamResult = new StreamResult(stringWriter);
        marshaller.marshal(o, streamResult);
        byte[] textBytes = stringWriter.toString().getBytes( Charset.forName("utf-8"));
        byte[] networkBytesOrder = getNetworkBytesOrder(textBytes.length);
        byte[] appidBytes = property.getAppid().getBytes( Charset.forName("utf-8"));
        byteCollector.addBytes(randomStrBytes);
        byteCollector.addBytes(networkBytesOrder);
        byteCollector.addBytes(textBytes);
        byteCollector.addBytes(appidBytes);
        // ... + pad: 使用自定义的填充方式对明文进行补位填充
        byte[] padBytes = PKCS7Encoder.encode(byteCollector.size());
        byteCollector.addBytes(padBytes);
        // 获得最终的字节流, 未加密
        byte[] unencrypted = byteCollector.toBytes();
        // 设置加密模式为AES的CBC模式

        try {
            Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
            byte[] aesKey = Base64.decodeBase64(property.getAesKey() + "=");
            SecretKeySpec keySpec = new SecretKeySpec(aesKey, "AES");
            IvParameterSpec iv = new IvParameterSpec(aesKey, 0, 16);
            cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv);
            // 加密
            byte[] encrypted = cipher.doFinal(unencrypted);
            // 使用BASE64对加密后的字符串进行编码
            String base64Encrypted = Base64.encodeBase64String(encrypted);
            String timeStamp = System.currentTimeMillis() + "";
            String nonce = System.currentTimeMillis() + "";
            String[] strs = {property.getToken(), timeStamp, nonce, base64Encrypted};
            Arrays.sort(strs);
            StringBuffer sb = new StringBuffer();
            for (String string : strs) {
                sb.append(string);
            }
            //数字签名
            MessageDigest.getInstance("SHA-1").digest(StringUtils.getBytesUtf8(sb.toString()));
            String signature = String.valueOf( Hex.encode(MessageDigest.getInstance("SHA-1").digest(StringUtils.getBytesUtf8(sb.toString()))));
            String result = XMLParse.generate(base64Encrypted, signature, timeStamp, nonce);
            httpOutputMessage.getBody().write(result.getBytes());
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (NoSuchPaddingException e) {
            e.printStackTrace();
        } catch (BadPaddingException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        } catch (IllegalBlockSizeException e) {
            e.printStackTrace();
        } catch (InvalidAlgorithmParameterException e) {
            e.printStackTrace();
        }


    }
}
